import type { BlockState } from '~/types'

const directions = [
  [-1, -1],
  [-1, 0],
  [-1, 1],
  [0, -1],
  [0, 1],
  [1, -1],
  [1, 0],
  [1, 1],
]

type GameStatus = 'play' | 'won' | 'lost'

interface GameState {
  board: BlockState[][]
  mineGenerated: boolean
  status: GameStatus
  startMs: number
  endMs: number | null
}

export class GamePlay {
  state = ref() as Ref<GameState>
  constructor(
    public width: number,
    public height: number,
    public mines: number,
  ) {
    this.reset()
  }

  get board() {
    return this.state.value.board
  }

  get blocks() {
    return this.board.flat()
  }

  reset(
    width: number = this.width,
    height: number = this.height,
    mines: number = this.mines,
  ) {
    this.width = width
    this.height = height
    this.mines = mines
    this.state.value = {
      startMs: +Date.now(),
      board: Array.from({ length: this.height }, (_, y) =>
        Array.from({ length: this.width }, (_, x): BlockState => ({
          x,
          y,
          adjacentMines: 0,
          revealed: false,
        }))),
      mineGenerated: false,
      status: 'play',
      endMs: null,
    }
  }

  random(min: number, max: number) {
    return Math.random() * (max - min + 1) + min
  }

  randomInt(min: number, max: number) {
    return Math.floor(Math.random() * (max - min + 1)) + min
  }

  generateMines(state: BlockState[][], inital: BlockState) {
    const placeRandom = () => {
      const x = this.randomInt(0, this.width - 1)
      const y = this.randomInt(0, this.height - 1)
      const block = state[y][x]
      if (Math.abs(inital.x - block.x) <= 1 && Math.abs(inital.y - block.y) <= 1)
        return false
      if (block.mine)
        return false
      block.mine = true
      return true
    }
    Array.from({ length: this.mines }, () => null)
      .forEach(() => {
        let placed = false
        while (!placed) {
          placed = placeRandom()
        }
      })
    this.updateNumbers()
  }

  updateNumbers() {
    this.board.forEach((row) => {
      row.forEach((block) => {
        if (block.mine)
          return
        this.getSiblings(block).forEach((s) => {
          if (s.mine)
            block.adjacentMines++
        })
      })
    })
  }

  getSiblings(block: BlockState) {
    return directions.map(([dx, dy]) => {
      const x2: number = block.x + dx
      const y2: number = block.y + dy
      if (x2 < 0 || x2 >= this.width || y2 < 0 || y2 >= this.height) {
        return undefined
      }
      return this.board[y2][x2]
      // if (state[y2][x2].mine)
      //   block.adjacentMines += 1
    }).filter(Boolean) as BlockState[]
  }

  onClick(block: BlockState) {
    if (this.state.value.status !== 'play')
      return
    if (!this.state.value.mineGenerated) {
      this.generateMines(this.board, block)
      this.state.value.mineGenerated = true
    }
    if (block.flagged)
      return
    block.revealed = true
    if (block.mine) {
      this.onGameOver('lost')
      this.showAllMines()
      return
    }
    if (block.flagged)
      return
    this.expendZero(block)
  }

  onRightClick(block: BlockState) {
    if (this.state.value.status !== 'play')
      return
    if (block.revealed)
      return
    block.flagged = !block.flagged
  }

  expendZero(block: BlockState) {
    if (block.adjacentMines)
      return
    this.getSiblings(block).forEach((s) => {
      if (!s.revealed && !s.flagged) {
        s.revealed = true
        this.expendZero(s)
      }
    })
  }

  checkGameState() {
    if (!this.state.value.mineGenerated)
      return
    const blocks = this.board.flat()

    if (blocks.every(block => block.revealed || block.flagged || block.mine)) {
      if (blocks.some(block => !block.mine && block.flagged)) {
        this.onGameOver('lost')
      }
      else {
        this.onGameOver('won')
      }
    }
  }

  showAllMines() {
    this.board.flat().forEach((block) => {
      if (block.mine)
        block.revealed = true
    })
  }

  autoExpand(block: BlockState) {
    if (this.state.value.status !== 'play')
      return
    const sibings = this.getSiblings(block)
    const flags = sibings.reduce((a, b) => a + (b.flagged ? 1 : 0), 0)
    const noRevealed = sibings.reduce((a, b) => a + (!b.revealed && !b.flagged ? 1 : 0), 0)
    if (flags === block.adjacentMines) {
      sibings.forEach((s) => {
        // if (!s.revealed && !s.flagged)
        s.revealed = true
        if (s.mine && !s.flagged) {
          this.onGameOver('lost')
        }
      })
    }
    const mssingFlags = block.adjacentMines - flags
    if (noRevealed === mssingFlags) {
      sibings.forEach((s) => {
        if (!s.revealed && !s.flagged)
          s.flagged = true
      },
      )
    }
  }

  onGameOver(status: GameStatus) {
    this.state.value.status = status
    this.state.value.endMs = +Date.now()
    if (status === 'lost') {
      this.showAllMines()

      setTimeout(() => {
        alert('You lost')
      }, 10)
    }
  }
}
